/*
Name: Up/Down (Ascending/Descending) Particles Demo 2D
Author: M. Shieh
Version: 1.0
Notes: To include a menu/resource make sure to include your resource files before first time compiling. (Do your resources FIRST!)
Special thanks to: >Dustin Davis @ www.programmers-unlimited.com
                   >Jeff Molofe (NeHe) @ nehe.gamedev.net
                   >The people @ www.gamedev.net
*/

/*==============================================================================
Header Files and Global Defines
==============================================================================*/
#include "resource.h"   //Header File for Resource IDs and Headers

/*==============================================================================
Global Variables
==============================================================================*/
//~~~~~The Window's Variables
    const char *WinClass_name="Up_Down Demo";
    HDC	hDC=NULL;
    HGLRC hRC=NULL;
    HWND hwnd=NULL;
    HINSTANCE hInstance;
    float g_FrameInterval = 0.0f;

//~~~~~Control Variables
	bool done=FALSE;
    bool active=TRUE;
    struct option
    {
        unsigned int particle_count;
        int particle_speed;
        int first_x;
        int first_y;
        int second_x;
        int second_y;
        int red;
        int green;
        int blue;
    } options;

//~~~~~Computer Speed Compensation::nehe.gamedev.net/
    struct
    {
       __int64 frequency;
       float resolution;
       unsigned long mm_timer_start;
       unsigned long mm_timer_elapsed;
       bool performance_timer;
       __int64 performance_timer_start;
       __int64 performance_timer_elapsed;
    } timer;
    float speed_mod=0.0;
    
/*==============================================================================
FUNCTION PROTOTYPES
==============================================================================*/
//=====Resize Scene Based on Window Dimesions
void ReSizeGLScene(GLsizei width, GLsizei height);

//=====Timer Functions for Computer Speed Compensation
//Timer Inilization
void TimerInit();
//Timer Retriever
float TimerGetTime();

//=====Other Functions
//Framerate Calculation
void CalculateFrameRate();

//=====Dialog Callbacks
//Options
bool CALLBACK Options_Dialog(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);

//=====External Functions
//Load Bitmaps
extern bool LoadBMP();
//Particle Generator Inilization
extern bool xia_IniUpDown(int quantity, float size, float speed_edit);
//Particle Generator De-inilization
extern void xia_CleanUpDown();
//Scene Drawing
extern int DrawScene();

/*==============================================================================
OPENGL INITIALIZATION
==============================================================================*/
int InitGL()
{
    if(!LoadBMP())
    {
        return FALSE;
    }
	srand(time(NULL));
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
    glEnable(GL_BLEND);
//Set up options variables
    options.particle_count=300;
    options.particle_speed=10;
    options.first_x=-15;
    options.first_y=-10;
    options.second_x=15;
    options.second_y=10;
    options.red=10;
    options.green=40;
    options.blue=100;
//Initialize Up/Down Particle Generator
    xia_IniUpDown(options.particle_count, .005, 1);
    gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -150.0, 0.0, 1.0, 0.0);
    return TRUE;
}

/*==============================================================================
CALLBACK
==============================================================================*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
		case WM_ACTIVATE:
		{
			if (!HIWORD(wParam))
			{
				active=TRUE;
			}
			else
			{
				active=FALSE;
			}
			return 0;
		}
		case WM_COMMAND:
		{
			switch(LOWORD(wParam))
			{
			     case IDM_OPTIONS:
			     {
			   		 CreateDialog(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_OPTIONS), hwnd,(DLGPROC)Options_Dialog);
			     }
			     break;
			     case IDM_ABOUT:
			     {
                     MessageBox(hwnd, "Programmed by: M. Shieh\nVersion: 1.0", "Up/Down Particle Demo 2D", MB_OK);
			     }
			     break;
			     case IDM_EXIT:
			     {
					 PostQuitMessage(0);
					 return 0;
			     }
            }
        }
        break;
		case WM_SYSCOMMAND:
		{
			switch (wParam)
			{
				case SC_SCREENSAVE:
				case SC_MONITORPOWER:
				return 0;
			}
			break;
		}
		case WM_SIZE:
		{
			ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
			return 0;
		}
		case WM_DESTROY:
		case WM_CLOSE:
		{
			PostQuitMessage(0);
			return 0;
		}
	}
	return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*==============================================================================
OPTIONS CALLBACK
==============================================================================*/
bool CALLBACK Options_Dialog(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    switch(message)
	{
		case WM_INITDIALOG:
		{
	    //Assign Values to edit Controls
	        SetDlgItemInt(hwnd, IDC_OPTIONS_COUNT, options.particle_count, FALSE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_SPEED, options.particle_speed, FALSE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_TOPX, options.first_x, TRUE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_TOPY, options.first_y, TRUE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_BOTTOMX, options.second_x, TRUE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_BOTTOMY, options.second_y, TRUE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_RED, options.red, FALSE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_GREEN, options.green, FALSE);
	        SetDlgItemInt(hwnd, IDC_OPTIONS_BLUE, options.blue, FALSE);
        //Set Character Limit for Edit Boxes
            SendDlgItemMessage(hwnd, IDC_OPTIONS_COUNT, EM_LIMITTEXT, 5, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_SPEED, EM_LIMITTEXT, 3, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_TOPX, EM_LIMITTEXT, 5, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_TOPY, EM_LIMITTEXT, 5, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_BOTTOMX, EM_LIMITTEXT, 5, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_BOTTOMY, EM_LIMITTEXT, 5, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_RED, EM_LIMITTEXT, 3, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_GREEN, EM_LIMITTEXT, 3, 0);
            SendDlgItemMessage(hwnd, IDC_OPTIONS_BLUE, EM_LIMITTEXT, 3, 0);
		}
		return TRUE;
		case WM_COMMAND:
		{
			switch(LOWORD(wparam))
            {
               case IDC_OPTIONS_OK:
               {
               //Obtain contents of the edit boxes
                   options.particle_count=GetDlgItemInt(hwnd, IDC_OPTIONS_COUNT, NULL, FALSE);
                   options.particle_speed=GetDlgItemInt(hwnd, IDC_OPTIONS_SPEED, NULL, FALSE);
                   options.first_x=GetDlgItemInt(hwnd, IDC_OPTIONS_TOPX, NULL, TRUE);
                   options.first_y=GetDlgItemInt(hwnd, IDC_OPTIONS_TOPY, NULL, TRUE);
                   options.second_x=GetDlgItemInt(hwnd, IDC_OPTIONS_BOTTOMX, NULL, TRUE);
                   options.second_y=GetDlgItemInt(hwnd, IDC_OPTIONS_BOTTOMY, NULL, TRUE);
                   options.red=GetDlgItemInt(hwnd, IDC_OPTIONS_RED, NULL, FALSE);
                   options.green=GetDlgItemInt(hwnd, IDC_OPTIONS_GREEN, NULL, FALSE);
                   options.blue=GetDlgItemInt(hwnd, IDC_OPTIONS_BLUE, NULL, FALSE);
               //Clean memory allocation and allocate memory again
                   xia_CleanUpDown();
                   xia_IniUpDown(options.particle_count, .005, 1);
               //End the dialog box
                   EndDialog(hwnd,0);
               }
               break;
               case IDC_OPTIONS_CANCEL:
               {
                   EndDialog(hwnd,0);
               }
               break;
            }
		}
		break;
	}
	return FALSE;
}

/*==============================================================================
FUNCTION DEFINITIONS
==============================================================================*/
//Resize Scene Based on Window Dimesions::nehe.gamedev.net/
void ReSizeGLScene(GLsizei width, GLsizei height)
{
	if (height == 0)
	{
		height=1;
	}
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

//Clean up::nehe.gamedev.net/
void KillGLWindow()
{
	if(hRC)
	{
		if(!wglMakeCurrent(NULL,NULL))
		{
			MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}

		if(!wglDeleteContext(hRC))
		{
			MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}
		hRC=NULL;
	}
	if(hDC && !ReleaseDC(hwnd,hDC))
	{
		MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hDC=NULL;
	}
	if(hwnd && !DestroyWindow(hwnd))
	{
		MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hwnd=NULL;
	}
	if(!UnregisterClass(WinClass_name,hInstance))
	{
		MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		hInstance=NULL;
	}
}

//Initiate Timer Function::nehe.gamedev.net/
void TimerInit(void)
{
	memset(&timer, 0, sizeof(timer));
	if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency))
	{
		timer.performance_timer	= FALSE;
		timer.mm_timer_start	= timeGetTime();
		timer.resolution		= 1.0f/1000.0f;
		timer.frequency			= 1000;
		timer.mm_timer_elapsed	= timer.mm_timer_start;
	}
	else
	{
		QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);
		timer.performance_timer = TRUE;
		timer.resolution = (float)(((double)1.0f)/((double)timer.frequency));
		timer.performance_timer_elapsed	= timer.performance_timer_start;
	}
}

//Very Precise Get Time Function::nehe.gamedev.net/
float TimerGetTime()
{
	__int64 time;
	if(timer.performance_timer)
	{
		QueryPerformanceCounter((LARGE_INTEGER *) &time);
		return((float)(time-timer.performance_timer_start)*timer.resolution)*1000.0f;
	}
	else
	{
		return((float)(timeGetTime()-timer.mm_timer_start)*timer.resolution)*1000.0f;
	}
}

//Calculate the framerate funcion::www.gametutorials.com
void CalculateFrameRate()
{
	static float framesPerSecond    = 0.0f;
    static float lastTime			= 0.0f;
	static char strFrameRate[50] = {0};

	static float frameTime = 0.0f;
    float currentTime = timeGetTime() * 0.001f;
 	g_FrameInterval = currentTime - frameTime;
	frameTime = currentTime;
    ++framesPerSecond;
    if( currentTime - lastTime > 1.0f )
    {
	    lastTime = currentTime;
		sprintf(strFrameRate, "Ascending/Descending Particle Demo [FPS: %d]", int(framesPerSecond));
		SetWindowText(hwnd, strFrameRate);
        framesPerSecond = 0;
    }
}

/*==============================================================================
APPLICATION WINDOW CREATION
==============================================================================*/
//CREATE OPENGL WINDOW
bool CreateGLWindow(char* title, int width, int height, int bits)
{
	GLuint		PixelFormat;
	WNDCLASS	wc;
	DWORD		dwExStyle;
	DWORD		dwStyle;
	RECT		WindowRect;
	WindowRect.left = (long)0;
	WindowRect.right = (long)width;
	WindowRect.top = (long)0;
	WindowRect.bottom = (long)height;
	hInstance=GetModuleHandle(NULL);
	//Fill in Window Class Structure
	wc.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc		= (WNDPROC) WndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= hInstance;
	wc.hIcon			= NULL;
	wc.hCursor			= LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground	= NULL;
	wc.lpszMenuName		= MAKEINTRESOURCE(IDR_MENU);
	wc.lpszClassName	= WinClass_name;
	if (!RegisterClass(&wc))
	{
		MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}
	dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
	dwStyle=WS_OVERLAPPEDWINDOW;
	AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
	//Create The Window
	if (!(hwnd=CreateWindowEx(	dwExStyle,
								WinClass_name,
								title,
								dwStyle |
								WS_CLIPSIBLINGS |
								WS_CLIPCHILDREN,
								0, 0,
								WindowRect.right-WindowRect.left,
								WindowRect.bottom-WindowRect.top,
								NULL,
								NULL,
								hInstance,
								NULL)))
	{
		KillGLWindow();
		MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}
	static	PIXELFORMATDESCRIPTOR pfd=
	{
		sizeof(PIXELFORMATDESCRIPTOR),				// Size Of This Pixel Format Descriptor
		1,											// Version Number
		PFD_DRAW_TO_WINDOW |						// Format Must Support Window
		PFD_SUPPORT_OPENGL |						// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,							// Must Support Double Buffering
		PFD_TYPE_RGBA,								// Request An RGBA Format
		bits,										// Select Our Color Depth
		0, 0, 0, 0, 0, 0,							// Color Bits Ignored
		0,											// No Alpha Buffer
		0,											// Shift Bit Ignored
		0,											// No Accumulation Buffer
		0, 0, 0, 0,									// Accumulation Bits Ignored
		16,											// 16Bit Z-Buffer (Depth Buffer)
		0,											// No Stencil Buffer
		0,											// No Auxiliary Buffer
		PFD_MAIN_PLANE,								// Main Drawing Layer
		0,											// Reserved
		0, 0, 0										// Layer Masks Ignored
	};

	if (!(hDC=GetDC(hwnd)))
	{
		KillGLWindow();
		MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}

	if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
	{
		KillGLWindow();
		MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}

	if(!SetPixelFormat(hDC,PixelFormat,&pfd))
	{
		KillGLWindow();
		MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}

	if (!(hRC=wglCreateContext(hDC)))
	{
		KillGLWindow();
		MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}

	if(!wglMakeCurrent(hDC,hRC))
	{
		KillGLWindow();
		MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}
	ShowWindow(hwnd,SW_SHOW);
	SetForegroundWindow(hwnd);
	SetFocus(hwnd);
	ReSizeGLScene(width, height);
	if (!InitGL())
	{
		KillGLWindow();
		MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;
	}
	return TRUE;
}

/*==============================================================================
//APPLICATION MAIN ENTRY POINT
==============================================================================*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG	msg;
//Check for more than 1 instance
    HANDLE hMutex = CreateMutex(NULL, TRUE, WinClass_name);
    if(GetLastError() == ERROR_ALREADY_EXISTS)
    {
        MessageBox(hwnd,"Program is already running","Warning",MB_OK | MB_ICONERROR);
        return 0;
    }
	if(!done)
	{
	    if (!CreateGLWindow("Ascending/Descending Particle Demo [FPS: ]",640,480,16))
	    {
			return 0;
	    }
	}
	TimerInit();
//Windows Message Loop
	while(!done)
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				done=TRUE;
			}
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else
		{
		    float start=TimerGetTime();
			if (active && !DrawScene())
			{
				done=TRUE;
			}
			else
			{
                Sleep(0);
				SwapBuffers(hDC);
				CalculateFrameRate();
			}
			speed_mod=(TimerGetTime()-start)/1000.0f;
		}
	}
//Cleanup when loop exits
    xia_CleanUpDown();
	KillGLWindow();
	return (msg.wParam);
}
